package org.andengine.util;

import android.annotation.TargetApi;
import android.content.Context;
import android.net.DhcpInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.os.Build;

import org.andengine.util.adt.array.ArrayUtils;
import org.andengine.util.exception.AndEngineException;
import org.andengine.util.exception.MethodNotFoundException;
import org.andengine.util.system.SystemUtils;

import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Enumeration;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @author Nicolas Gramlich
 * @since 16:54:01 - 20.03.2011
 */
public final class WifiUtils {
    // ===========================================================
    // Constants
    // ===========================================================

    private static final String IP_DEFAULT = "0.0.0.0";
    private static final String[] HOTSPOT_NETWORKINTERFACE_NAMES = {"wl0.1", "wlan0"};
    private static final String MULTICASTLOCK_NAME_DEFAULT = "AndEngineMulticastLock";

    // ===========================================================
    // Fields
    // ===========================================================

    // ===========================================================
    // Constructors
    // ===========================================================

    private WifiUtils() {

    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    // ===========================================================
    // Methods
    // ===========================================================

    public static WifiManager getWifiManager(final Context pContext) {
        return (WifiManager) pContext.getSystemService(Context.WIFI_SERVICE);
    }

    public static boolean isWifiEnabled(final Context pContext) {
        return WifiUtils.getWifiManager(pContext).isWifiEnabled();
    }

    private static void setWifiEnabled(final Context pContext, final boolean pEnabled) {
        WifiUtils.getWifiManager(pContext).setWifiEnabled(pEnabled);
    }

    public static String getWifiSSID(final Context pContext) {
        return WifiUtils.getWifiManager(pContext).getConnectionInfo().getSSID();
    }

    public static String getWifiSSID(final Context pContext, final boolean pStripQuotes) {
        final String wifiSSID = WifiUtils.getWifiSSID(pContext);

        if (pStripQuotes) {
            return WifiUtils.stripQuotes(wifiSSID);
        } else {
            return wifiSSID;
        }
    }

    public static byte[] getWifiIPv4AddressRaw(final Context pContext) {
        return IPUtils.ipv4AddressToIPAddress(WifiUtils.getWifiManager(pContext).getConnectionInfo().getIpAddress());
    }

    public static String getWifiIPv4Address(final Context pContext) throws WifiUtilsException {
        try {
            return IPUtils.ipAddressToString(WifiUtils.getWifiIPv4AddressRaw(pContext));
        } catch (final UnknownHostException e) {
            throw new WifiUtilsException("Unexpected error!", e);
        }
    }

    public static boolean isWifiIPAddressValid(final Context pContext) {
        return WifiUtils.getWifiManager(pContext).getConnectionInfo().getIpAddress() != 0;
    }


    /**
     * The check currently performed is not sufficient, as some carriers disabled this feature manually!
     */
    public static boolean isWifiHotspotSupported(final Context pContext) {
        if (SystemUtils.isAndroidVersionOrLower(Build.VERSION_CODES.ECLAIR_MR1)) {
            return false;
        } else {
            final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);
            try {
                final Method WifiManager_isWifiApEnabled = wifiManager.getClass().getMethod("isWifiApEnabled");
                return WifiManager_isWifiApEnabled != null;
            } catch (final Throwable t) {
                return false;
            }
        }
    }

    public static boolean isWifiHotspotEnabled(final Context pContext) throws WifiUtilsException {
        final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

        try {
            final Method WifiManager_isWifiApEnabled = wifiManager.getClass().getMethod("isWifiApEnabled");
            if (WifiManager_isWifiApEnabled == null) {
                throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".isWifiApEnabled()"));
            } else {
                final boolean result = (Boolean) WifiManager_isWifiApEnabled.invoke(wifiManager);
                return result;
            }
        } catch (final Throwable t) {
            throw new WifiUtilsException(t);
        }
    }

    public static boolean isWifiHotspotEnabled(final Context pContext, final boolean pDefault) {
        try {
            return WifiUtils.isWifiHotspotEnabled(pContext);
        } catch (final WifiUtilsException e) {
            return pDefault;
        }
    }

    public static boolean setWifiHostpotEnabled(final Context pContext, final boolean pEnabled) throws WifiUtilsException {
        return WifiUtils.setWifiHostpotEnabled(pContext, null, pEnabled);
    }

    public static boolean setWifiHostpotEnabled(final Context pContext, final WifiConfiguration pWifiConfiguration, final boolean pEnabled) throws WifiUtilsException {
        final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

        try {
            if (pEnabled) {
                WifiUtils.setWifiEnabled(pContext, false);
            }

            final Method WifiManager_setWifiApEnabled = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
            if (WifiManager_setWifiApEnabled == null) {
                throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".setWifiApEnabled(" + WifiConfiguration.class.getSimpleName() + ", " + boolean.class.getSimpleName() + ")"));
            } else {
                final boolean result = (Boolean) WifiManager_setWifiApEnabled.invoke(wifiManager, pWifiConfiguration, pEnabled);
                return result;
            }
        } catch (final Throwable t) {
            throw new WifiUtilsException(t);
        }
    }

    public static WifiConfiguration getWifiHotspotConfiguration(final Context pContext) throws WifiUtilsException {
        final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

        try {
            final Method WifiManager_getWifiApState = wifiManager.getClass().getMethod("getWifiApConfiguration");
            if (WifiManager_getWifiApState == null) {
                throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".getWifiApConfiguration()"));
            } else {
                return (WifiConfiguration) WifiManager_getWifiApState.invoke(wifiManager);
            }
        } catch (final Throwable t) {
            throw new WifiUtilsException(t);
        }
    }

    public static String getWifiHotspotSSID(final Context pContext) throws WifiUtilsException {
        return WifiUtils.getWifiHotspotConfiguration(pContext).SSID;
    }

    public static String getWifiHotspotSSID(final Context pContext, final boolean pStripQuotes) throws WifiUtilsException {
        final String wifiHotspotSSID = WifiUtils.getWifiHotspotSSID(pContext);

        if (pStripQuotes) {
            return WifiUtils.stripQuotes(wifiHotspotSSID);
        } else {
            return wifiHotspotSSID;
        }
    }

    public static WifiHotspotState getWifiHotspotState(final Context pContext) throws WifiUtilsException {
        final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

        try {
            final Method WifiManager_getWifiApState = wifiManager.getClass().getMethod("getWifiApState");
            if (WifiManager_getWifiApState == null) {
                throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".getWifiApState()"));
            } else {
                final int result = (Integer) WifiManager_getWifiApState.invoke(wifiManager);

                return WifiHotspotState.fromWifiApState(result);
            }
        } catch (final Throwable t) {
            throw new WifiUtilsException(t);
        }
    }

    public static boolean isWifiHotspotRunning(final Context pContext) throws WifiUtilsException {
        return WifiUtils.getWifiHotspotState(pContext) == WifiHotspotState.ENABLED;
    }

    /**
     * @return prefers to return an IPv4 address if found, otherwise an IPv6 address.
     * @throws org.andengine.util.WifiUtils.WifiUtilsException
     */
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    public static byte[] getWifiHotspotIPAddressRaw() throws WifiUtilsException {
        try {
            byte[] ipv6Address = null;

            final Enumeration<NetworkInterface> networkInterfaceEnumeration = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaceEnumeration.hasMoreElements()) {
                final NetworkInterface networkInterface = networkInterfaceEnumeration.nextElement();
                if (SystemUtils.isAndroidVersionOrLower(Build.VERSION_CODES.FROYO) || !networkInterface.isLoopback()) {
                    final String networkInterfaceName = networkInterface.getName();

                    if (ArrayUtils.contains(WifiUtils.HOTSPOT_NETWORKINTERFACE_NAMES, networkInterfaceName)) {
                        final Enumeration<InetAddress> inetAddressEnumeration = networkInterface.getInetAddresses();
                        while (inetAddressEnumeration.hasMoreElements()) {
                            final InetAddress inetAddress = inetAddressEnumeration.nextElement();
                            if (!inetAddress.isLoopbackAddress()) {
                                final byte[] ipAddress = inetAddress.getAddress();
                                if (ipAddress.length == IPUtils.IPV4_LENGTH) {
                                    return ipAddress;
                                } else {
                                    ipv6Address = ipAddress;
                                }
                            }
                        }
                    }
                }
            }

            if (ipv6Address != null) {
                return ipv6Address;
            } else {
                throw new WifiUtilsException("No IP bound to '" + Arrays.toString(WifiUtils.HOTSPOT_NETWORKINTERFACE_NAMES) + "'!");
            }
        } catch (final SocketException e) {
            throw new WifiUtilsException("Unexpected error!", e);
        }
    }

    public static String getWifiHotspotIPAddress() throws WifiUtilsException {
        try {
            return IPUtils.ipAddressToString(WifiUtils.getWifiHotspotIPAddressRaw());
        } catch (final UnknownHostException e) {
            throw new WifiUtilsException("Unexpected error!", e);
        }
    }

    public static boolean isWifiHotspotIPAddressValid() throws WifiUtilsException { // TODO!
        return !WifiUtils.IP_DEFAULT.equals(WifiUtils.getWifiHotspotIPAddress());
    }

    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    public static byte[] getEmulatorIPAddressRaw() throws WifiUtilsException {
        try {
            byte[] ipv6Address = null;

            final Enumeration<NetworkInterface> networkInterfaceEnumeration = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaceEnumeration.hasMoreElements()) {
                final NetworkInterface networkInterface = networkInterfaceEnumeration.nextElement();
                if (SystemUtils.isAndroidVersionOrLower(Build.VERSION_CODES.FROYO) || !networkInterface.isLoopback()) {
                    final Enumeration<InetAddress> inetAddressEnumeration = networkInterface.getInetAddresses();
                    while (inetAddressEnumeration.hasMoreElements()) {
                        final InetAddress inetAddress = inetAddressEnumeration.nextElement();
                        if (!inetAddress.isLoopbackAddress()) {
                            final byte[] ipAddress = inetAddress.getAddress();
                            if (ipAddress.length == IPUtils.IPV4_LENGTH) {
                                return ipAddress;
                            } else {
                                ipv6Address = ipAddress;
                            }
                        }
                    }
                }
            }

            if (ipv6Address != null) {
                return ipv6Address;
            } else {
                throw new WifiUtilsException("No IP found that is not bound to localhost!");
            }
        } catch (final SocketException e) {
            throw new WifiUtilsException("Unexpected error!", e);
        }
    }

    public static String getEmulatorIPAddress() throws WifiUtilsException {
        try {
            return IPUtils.ipAddressToString(WifiUtils.getEmulatorIPAddressRaw());
        } catch (final UnknownHostException e) {
            throw new WifiUtilsException("Unexpected error!", e);
        }
    }

    public static boolean getEmulatorIPAddressValid() throws WifiUtilsException { // TODO!
        return !WifiUtils.IP_DEFAULT.equals(WifiUtils.getEmulatorIPAddress());
    }


    public static boolean isMulticastEnabled(final Context pContext) throws WifiUtilsException {
        final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

        try {
            final Method WifiManager_isMulticastEnabled = wifiManager.getClass().getMethod("isMulticastEnabled");
            if (WifiManager_isMulticastEnabled == null) {
                throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".isMulticastEnabled()"));
            } else {
                final boolean result = (Boolean) WifiManager_isMulticastEnabled.invoke(wifiManager);
                return result;
            }
        } catch (final Throwable t) {
            throw new WifiUtilsException(t);
        }
    }

    public static boolean isMulticastEnabled(final Context pContext, final boolean pDefault) {
        try {
            return WifiUtils.isMulticastEnabled(pContext);
        } catch (final WifiUtilsException e) {
            return pDefault;
        }
    }

    public static MulticastLock aquireMulticastLock(final Context pContext) {
        return WifiUtils.aquireMulticastLock(pContext, WifiUtils.MULTICASTLOCK_NAME_DEFAULT);
    }

    public static MulticastLock aquireMulticastLock(final Context pContext, final String pMulticastLockName) {
        final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);
        final MulticastLock multicastLock = wifiManager.createMulticastLock(pMulticastLockName);
        multicastLock.setReferenceCounted(true);
        multicastLock.acquire();
        return multicastLock;
    }

    public static void releaseMulticastLock(final MulticastLock pMulticastLock) {
        if (pMulticastLock.isHeld()) {
            pMulticastLock.release();
        }
    }

    public static byte[] getBroadcastIPAddressRaw(final Context pContext) throws WifiUtilsException {
        final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);
        final DhcpInfo dhcp = wifiManager.getDhcpInfo();
        // TODO handle null somehow...

        final int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
        final byte[] broadcastIP = new byte[IPUtils.IPV4_LENGTH];
        for (int k = 0; k < IPUtils.IPV4_LENGTH; k++) {
            broadcastIP[k] = (byte) ((broadcast >> (k * 8)) & 0xFF);
        }
        return broadcastIP;
    }

    private static String stripQuotes(final String pString) {
        if (pString == null) {
            return pString;
        } else {
            final int stringLength = pString.length();
            if (stringLength >= 2) {
                if (pString.startsWith("\"") && pString.endsWith("\"")) {
                    return pString.substring(1, stringLength - 1);
                } else {
                    return pString;
                }
            } else {
                return pString;
            }
        }
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public static enum WifiHotspotState {
        // ===========================================================
        // Elements
        // ===========================================================

        DISABLING,
        DISABLED,
        ENABLING,
        ENABLED,
        FAILED;

        // ===========================================================
        // Constants
        // ===========================================================

        // ===========================================================
        // Fields
        // ===========================================================

        // ===========================================================
        // Constructors
        // ===========================================================

        public static WifiHotspotState fromWifiApState(final int pWifiApState) throws WifiUtilsException {
            if (SystemUtils.isAndroidVersionOrHigher(Build.VERSION_CODES.ICE_CREAM_SANDWICH)) {
                switch (pWifiApState) {
                    case 10:
                        return DISABLING;
                    case 11:
                        return DISABLED;
                    case 12:
                        return ENABLING;
                    case 13:
                        return ENABLED;
                    case 14:
                        return FAILED;
                    default:
                        throw new WifiUtilsException("TODO...");
                }
            } else {
                switch (pWifiApState) {
                    case 0:
                        return DISABLING;
                    case 1:
                        return DISABLED;
                    case 2:
                        return ENABLING;
                    case 3:
                        return ENABLED;
                    case 4:
                        return FAILED;
                    default:
                        if (pWifiApState >= 10) {
                            return WifiHotspotState.fromWifiApState(pWifiApState - 10);
                        } else {
                            throw new WifiUtilsException("TODO...");
                        }
                }
            }
        }

        // ===========================================================
        // Getter & Setter
        // ===========================================================

        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================

        // ===========================================================
        // Methods
        // ===========================================================

        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }

    public static class WifiUtilsException extends AndEngineException {
        // ===========================================================
        // Constants
        // ===========================================================

        private static final long serialVersionUID = 1108697754015225179L;

        // ===========================================================
        // Constructors
        // ===========================================================

        public WifiUtilsException() {

        }

        public WifiUtilsException(final Throwable pThrowable) {
            super(pThrowable);
        }

        private WifiUtilsException(final String pMessage) {
            super(pMessage);
        }

        private WifiUtilsException(final String pMessage, final Throwable pThrowable) {
            super(pMessage, pThrowable);
        }
    }
}
